/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2001 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef __sel_resource_h__
#define __sel_resource_h__

#include <vector>
#include <string>
#include <list>
#include "../base/sdl.h"
#include "../base/rwops.h"

namespace SEL
{

//! This class presents an option that belongs to a resource.
/*!
  The resource property has associated name and values with it.
  The number of values is unlimited. The internal representation
  of a value is a string, but it can be converted to numbers
  on fly. The resource properties are organized in a 
  ResourceProperties class.
 */
class ResourceProperty
{
public:
	//! Create a new empty resource property.
	ResourceProperty ();
	
	//! Create a new resource property with given name.
	/*!
	  The class is created without values.
	  \param name the name of the new property.
	 */
	ResourceProperty (const std::string &name);
	
	//! Create a new resource property with given name and value.
	/*!
	  \param name the name of the new property.
	  \param value the first value of the new property.
	 */
	ResourceProperty (const std::string &name, const std::string &value);
	
	//! Destroys the property.
	~ResourceProperty () { }
	
	//! Get the property name.
	const std::string &name () const	 { return m_name; }

	//! Set the property name.
	void setName (const std::string &name)	 { m_name = name; }

	//! Clear the values of the property.
	void clear ();
	
	//! Get the value with given index.
	/*!
	  \return the value with given \a index. If the \a index parameter
	  is omitted, it will return the first value.
	  \param index the index of the requested value.
	 */
	const std::string &value (int index = 0) const 
						 { return m_values[index]; }
	
	//! Set the value with given index.
	/*!
	  Sets the current value with given index to \a value.
	  If the \a index parameter is omitted, it will set the first value.
	  \note a value with index \a index must exist before setting it.
	  If the value does not exist, use the ResourceProperty::addValue 
	  function.
	  
	  \param value the new value.
	  \param index the index of the value to be set.
	  \sa addValue ()
	 */
	void setValue (const std::string &value, int index=0);
	
	//! Append a value.
	/*!
	  Appends a value \a value to the current values.
	  \param value the new value to be added.
	 */
	void addValue (const std::string &value) { m_values.push_back (value); }
	
	//! Get the number of values.
	int count () const			 { return m_values.size (); }
	
	//! Get the values.
	std::vector<std::string> &values ()	 { return m_values; }
	
	//! Get the values.
	const std::vector<std::string> &values () const
						 { return m_values; }
	
	//! Query, whether the value with given \a index is an integer.
	/*!
	  \note
	  The number contained by the string can be represented either
	  decimal or hexadecimal number.
	 */
	bool isInteger (int index = 0) const;
	
	//! Query, whether the value with given \a index is a floating point
	//! number.
	bool isFloat (int index = 0) const;
	
	//! Get the value with given \a index as integer.
	/*!
	  \note
	  The number contained by the string can be represented either
	  decimal or hexadecimal number.
	 */
	int asInteger (int index = 0) const;
	
	//! Get the value with given \a index as float.
	float asFloat (int index = 0) const;

protected:
	std::string m_name;
	std::vector<std::string> m_values;
};

//! This class represents a group of ResourceProperty objects.
class ResourceProperties
{
public:
	//! Create a new resource properties.
	ResourceProperties () { }
	
	//! Destroy the object.
	~ResourceProperties () { }
	
	//! Get a reference to property with given name.
	/*!
	  \param name name of the desired property.
	  \note if the requested property does not exist,
	  an exception is thrown.
	 */
	ResourceProperty & get (const std::string & name);

	//! Get a constant reference to property with given name.
	/*!
	  \param name name of the desired property.
	  \note if the requested property does not exist,
	  an exception is thrown.
	 */
	const ResourceProperty & get (const std::string & name) const;

	
	//! Query wheter the property with the given name exits
	//! in the list.
	/*!
	  \param name the name of the queried property.
	 */
	bool exists (const std::string &name) const;

	//! Return the number of properties in the list.
	int count () const		{ return m_properties.size (); }
	
	//! Add a property to the group.
	/*!
	  \param property the property to be appended.
	 */
	void add (const ResourceProperty &property);
	
	//! Get the properties in the group.
	std::vector<ResourceProperty> &properties ()	{ return m_properties; }
	
	//! Get the properties in the group.
	const std::vector<ResourceProperty> &properties () const
						    { return m_properties; }

protected:
	std::vector<ResourceProperty> m_properties;
};

class ResourceManager;

//! This class represents a resource in the ResourceManager.
/*!
  This is the default resource type. It does not contain any data.
  All other resource types are derived from this one.
  You can also derive your own resource types (e.g. animations, ...)
  
  After you derive your own resource type, you must register it
  using the ResourceTypeRegister class.
  
  Streamable resources are also supported.
 */
class Resource 
{
public:
	//! Create a new resource specifing it's \a name, \a location and
	//! \a properties.
	/*!
	  Don't forget to set the resource type (m_type) in your own
	  constructor.
	 */
	Resource (const std::string &name,
		  const std::string &location,
		  const ResourceProperties &properties);
	
	//! Default destructor.
	virtual ~Resource ();

	//! Return the resource type.
	const std::string &type () const	{ return m_type; }
	
	//! Set the resource type.
	/*!
	  \param type the new type of the resource.
	 */
	void setType (const std::string &type);
	
	//! Return the resource name.
	const std::string &name () const	{ return m_name; }
	
	//! Set the resource name.
	/*!
	  \param name the new name of the resource.
	 */
	void setName (const std::string &name);
	
	//! Return the resource location.
	const std::string &location () const	{ return m_location; }
	
	//! Set the resource location.
	/*!
	  \param location the new resource location.
	 */
	void setLocation (const std::string &location);
	
	//! Get the reference to the properties associated with the resource.
	ResourceProperties &properties () 	{ return m_properties; }

	//! Get the reference to the properties associated with the resource.
	const ResourceProperties &properties () const
						{ return m_properties; }

	//! Load the resource.
	/*!
	  Loading is only possible if the resource has some data. 
	  (like ResourceSurface, etc.)
	 */
	void load ();	

	//! The implementation of the loading.
	virtual void load (RWops & ops)		{ }
	
	//! Free the loaded data.
	virtual void free () 			{ }
	
	//! Return whether the data is loaded or not.
	virtual bool loaded () const		{ return false; }
	
	//! Return whether the resource has data or not.
	/*!
	  The resource data is stored in the file pointed to by
	  the location, or in the compiled resource file.
	 */
	virtual bool hasData ()	const		{ return false; }
	
	//! Return the pointer to the resource data.
	virtual void *data ()			{ return NULL; }

	//! Return const pointer to the resource data.
	virtual const void *data () const	{ return NULL; }
	
	//! Copy the data from the source rwops to the destination rwops.
	/*!
	  The default implementation makes a raw copy of the source.
	  Reimplement this, if you want to modify the source data that
	  will be copied to the data file.
	 */
	virtual void copyData (RWops &src, RWops &dst);

protected:
	friend class ResourceManager;
	
	std::string m_type;
	std::string m_name;
	std::string m_location;
	ResourceProperties m_properties;

	ResourceManager *m_manager;

private:
	//! The data offset after the header in the datafile. Internal.
	Uint32 m_data_offset;
};

class Tokenizer;

//! The resource manager contains an organized set of resources.
/*!
  The resource manager is used to retrieve the resources from a given
  source. This can be either the resource script or a compiled datafile
  with all resources included.
  
  The resources are organized in sections. To speed up loading, you can
  load all resources in the manager (or in a section) at once.
  
  If you want to compile a datafile, you must first write a resource script,
  load it and the use the ResourceManager::createDataFile () function to
  write the data file.
  
  The loading of a resource should look like this:
  \code
    ResourceManager manager ("testscript"); // for loading from as script
    // or ResourceManager manager ("testdatafile", true); // for loading from a datafile
    // optionally preload the resources
    manager.loadSection ("somesection/somesubsection");
    
    // and then get the data (e.g. Surface)
    Surface *surface = getSurface (manager.get("surface1"));
    // or
    ResourceSurface *rs = (ResourceSurface*)manager.get("surface2");
    Surface *surface2 = rs->surface();            
  \endcode
  \note
    You should not free the resource data manually. Instead use the free
    function of the resource.
 */
class ResourceManager
{
public:
	//! Create a new resource manager.
	/*!
	  \param filename the file name of either the resource script 
	         or the data file.
	  \param is_datafile specifies, whether the source is a datafile
	         or not.
	 */
	ResourceManager (const std::string & filename, 
			 bool is_datafile = false);

	//! Destroy the resource manager.
	/*!
	  \note This frees all the resources.
	 */
	~ResourceManager ();
	
	//! Load the resources.
	/*!
	  \note This is called automatically on manager creation so
	  there should be no need to call this manually.
	 */
	void loadResources ();

	//! Free the resources.
	/*!
	  \note This is called automatically on manager destruction so
	  there should be no need to call this manually.
	 */
	void freeResources ();

	//! Set an active prefix.
	/*!
	  The prefix is prepended to the resource name when requesting
	  the resource.
	  \param prefix the prefix
	  \param append whether the \a prefix should be appended to the
	   previous active prefix.
	 */
	void pushPrefix (const std::string & prefix, bool append = true);
	
	//! Remove the active prefix and set the previous one.
	void popPrefix ();
	
	//! Remove all the prefixes.
	void clearPrefix ();
	
	//! Return the requested resource.
	/*!
	  \return the pointer to the resource with given \a name or NULL,
	  if the resource could no be found and \a no_exception is true.
	  \param name the name of the resource. This is automatically
	   prepended with the active prefix.
	  \param no_exception blocks the exception throwing when the
	   resource could not be found.
	 */
	Resource *get (const std::string & name, bool no_exception = false);

	//! Return the requested constant resource.
	/*!
	  \return the pointer to the resource with given \a name or NULL,
	  if the resource could no be found and \a no_exception is true.
	  \param name the name of the resource. This is automatically
	   prepended with the active prefix.
	  \param no_exception blocks the exception throwing when the
	   resource could not be found.
	 */
	const Resource *get 
		(const std::string & name, bool no_exception = false) const;
	
	//! Create a RWops of the resource data file.
	/*!
	  The result must be freed manually. 
	  \note You shouldn't modify the virtual begin and virtual
	  size of the RWops, because it can be a part of the resource
	  data file.
	  
	  \param res the resource.
	 */
	RWops *createResourceRWops (const Resource & res) const;
	
	//! Write the current resources as a datafile
	/*!
	  This creates a datafile that contains all the resources,
	  their properties and data.	
	 */
	void createDataFile (const std::string & filename) const;
	
	//! Load the resource data of all resources.
	void loadAll ();
	
	//! Free the resource data of all resources.
	void freeAll ();
	
	//! Load the resource data of all resources that belong to a specified
	//! section.
	/*!
	  \param section the name of specified section.
	  \use_prefix whether the section name should be prepended with
	  the current prefix.
	 */
	void loadSection (const std::string & section, bool use_prefix = true);
	
	//! Load the resource data of all resources that belong to the current
	//! section. (Specified by the active prefix.)
	/*!
	  \note If no active prefix is set the data of all resources will
	   be loaded.
	 */
	void loadSection ();
	
	//! Free the resource data of all resources that belong to a specified
	//! section.
	/*!
	  \param section the name of specified section.
	  \use_prefix whether the section name should be prepended with
	  the current prefix.
	 */
	void freeSection (const std::string & section, bool use_prefix = true);
	
	//! Free the resource data of all resources that belong to the current
	//! section. (Specified by the active prefix.)
	/*!
	  \note If no active prefix is set the data of all resources will
	   be freed.
	 */
	void freeSection ();

protected:
	std::string m_filename;
	bool m_datafile;
	std::list<Resource*> m_resources;
	Tokenizer *m_tokenizer;
	std::stack<std::string> m_prefixes;
	Uint32 m_header_size;

	Resource *addResource (const std::string &type,
			       const std::string &name,
			       const std::string &location,
			       const ResourceProperties &properties);
	void parseError (const std::string & error);	

	void loadFromScript (RWops *ops, const std::string & filename);
	void saveDataFile (RWops *ops) const;
	void loadFromDataFile (RWops *ops);
};

//! The ResourceType class holds an information about resource types
//! and is used to create various resource types.
/*!
  This class is used internally. If you want to register your own
  resource type, use the ResourceTypeRegister class;
 */
class ResourceType
{
public:	
	//!
	ResourceType (const std::string & type);
	
	//!
	virtual ~ResourceType ();
	
	//!
	const std::string & type () const 		{ return m_type; }

	//!
	static std::list <ResourceType*> & types () 	{ return m_types; }
	
	//!
	virtual Resource *createResource (const std::string &name,
				  	  const std::string &location,
				  	  const ResourceProperties &properties) 
					  = 0;
					  
	//!
	static Resource *createResource (const std::string &type,
					 const std::string &name,
					 const std::string &location,
					 const ResourceProperties &properties);

protected:
	std::string m_type;
	static std::list <ResourceType*> m_types;
};

//! The purpose of the ResourceTypeRegister class is to register
//! (user) resource types.
/*!
  The only thing you have to do after creating your own Resource class
  is to register it this way:
  \code
    SurfaceTypeRegister <MyResource> MyResourceRegister ("my_resource");
  \endcode
  This should be (preferably) put somewhere in the beginning of your code.
 */
template <class TResource>
class ResourceTypeRegister : public ResourceType
{
public:
	//!
	ResourceTypeRegister (const std::string &type)
		:ResourceType (type)
	{
	}
	
	//!
	virtual TResource *createResource (const std::string &name,
				  	   const std::string &location,
				  	   const ResourceProperties &properties)
	{
		return new TResource (name, location, properties);
	}
};


} // namespace SEL

#endif // __sel_resource_h__
